C 语言 sizeof 与 strlen:编译期大小与运行时长度
sizeof 和 strlen 都在与"长度"打交道,初学 C 时很容易混用:拿 sizeof 去算字符串长度、或拿 strlen 去算缓冲区大小,都埋过不少雷。两者的差别不在"量什么",而在"什么时候量"——sizeof 是编译期由类型确定的运算符,strlen 是运行期逐字符扫描的库函数。本文从这个时机差异出发,讲清两者的行为边界与三个常见陷阱,适合刚开始写 C、或想复核基础概念的读者。
一个是运算符,一个是库函数
sizeof 是 C 语言的关键字级运算符,以字节为单位给出操作数的大小,不需要包含任何头文件(返回类型 size_t 的定义在 <stddef.h>,不过 <stdio.h>、<string.h> 等常用头文件里也有)。它在编译期就由类型确定结果,生成的机器码里往往只剩一个常量。
strlen() 是 <string.h> 提供的库函数,从给定地址开始逐字符扫描,直到遇到结尾的 '\0' 才停,返回的字符数不含这个终止符。扫描发生在运行期,代价与字符串实际长度成正比,也依赖字符串正确终止——没有 '\0' 它会一路读越界。
一句话区分:sizeof 量的是"这块内存有多大",strlen 量的是"这段内容有多长"。
同一个 "hello",两组不同的答案
用数组定义和用指针定义同一个字符串字面量,sizeof 给出的结果完全不同:
char a[] = "hello"; // 数组:6 个字节(5 个字符 + '\0')
char *p = "hello"; // 指针:变量里存的是字符串首地址
printf("%zu %zu\n", sizeof(a), strlen(a)); // 6 5
printf("%zu %zu\n", sizeof(p), strlen(p)); // 8 5(64 位系统上指针 8 字节) 四个结果的由来:
sizeof(a)是整个数组的字节数,包含'\0';strlen(a)数到'\0'为止,不包含它;sizeof(p)只是指针变量本身的大小,与它指向的字符串无关;strlen(p)沿地址扫描到'\0',结果与strlen(a)一致。
这组对照也解释了为什么"用 sizeof 算字符串长度"总是差一(多算了 '\0'),而"用 strlen 算缓冲区大小"则是类型层面的误解——它根本不反映内存块的大小。
数组传进函数就退化为指针
数组一旦出现在函数形参列表里,实际类型就是指针:int f(int a[2]) 与 int f(int* a) 声明的是同一个函数。因此函数内部对形参做 sizeof,得到的是指针大小,而不是调用方数组的大小:
void f(int a[10]) {
printf("%zu\n", sizeof(a)); // 8(64 位下的指针大小),不是 40
} 数组长度信息在传参边界上丢失了,需要长度就得作为独立参数传入。值得一提的是,sizeof 作用于数组本身正是数组"不退化"为指针的少数场景之一(另一个常见的例外是取地址 &),所以这个坑只出现在函数调用边界上,同一作用域内 sizeof(a) 仍然是完整数组的大小。
sizeof 不求值它的操作数
sizeof 的操作数是表达式时,表达式本身并不求值——sizeof(i++) 不会让 i 自增,编译器只根据表达式类型得出大小,结果还可以用在整型常量表达式里。这既有用(可以写 sizeof(x)/sizeof(x[0]) 这类数组长度惯用法,无副作用),也容易让人误判:调试时指望 sizeof "顺带"改个值是不可能的。
例外是 C99 的变长数组(VLA):数组长度要到运行期才知道,此时 sizeof 的操作数会被求值,结果也不再是编译期常量。
打印结果用 %zu
sizeof 和 strlen 的返回类型都是无符号的 size_t。C99 引入了 z 长度修饰符来配合它:无符号对应 %zu,有符号版本(如 ptrdiff_t 一类)对应 %zd。打印两者的返回值用 %zu 最准确;用 %d 接 size_t 属于类型不匹配,行为未定义,64 位平台上常见的乱码输出就来自这类错配。格式串各成分的完整说明可以参考 printf 转换说明与修饰符速查。
圆括号什么时候必须
sizeof 的圆括号规则取决于操作数是类型还是表达式:
- 操作数是类型时,圆括号必不可少:
sizeof(int); - 操作数是具体的量时,圆括号可有可无:
sizeof name、sizeof 6.28都合法。
尽管如此,建议所有情况都带上圆括号——与 sizeof name 相比,sizeof(name) 没有任何代价,还能避免在类型与表达式混用的场景里出错。
小结
- sizeof 是运算符,编译期由类型得出字节数;strlen 是库函数,运行期扫描到
'\0'得出字符数; - 数组与指针:sizeof 对数组是整块大小(含
'\0'),对指针是指针自身大小;strlen 两者一致; - 数组作函数参数退化为指针,函数内的 sizeof 拿不到原数组大小,长度要单独传;
- sizeof 不求值操作数(VLA 除外);
- 打印 size_t 用
%zu;类型与转换说明不匹配是未定义行为。